msg_tool\scripts\kirikiri\archive/
xp3.rs1use super::xp3pack::*;
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use anyhow::Result;
6use flate2::read::ZlibDecoder;
7use overf::wrapping;
8use std::io::{Read, Seek, SeekFrom, Take};
9use std::sync::{Arc, Mutex};
10use xp3::XP3Reader;
11use xp3::index::file::{IndexSegmentFlag, XP3FileIndex};
12
13pub use super::xp3pack::SegmenterConfig;
14
15pub fn parse_segmenter_config(str: &str) -> Result<SegmenterConfig> {
16 let parts: Vec<&str> = str.split(':').collect();
17 if parts.is_empty() {
18 return Ok(SegmenterConfig::default());
19 }
20 match parts[0].to_lowercase().as_str() {
21 "none" => Ok(SegmenterConfig::None),
22 "cdc" => {
23 if parts.len() != 4 {
24 return Err(anyhow::anyhow!(
25 "Invalid FastCDC segmenter config. Expected format: fastcdc,min_size,avg_size,max_size"
26 ));
27 }
28 let min_size = parse_size::parse_size(parts[1])?;
29 let avg_size = parse_size::parse_size(parts[2])?;
30 let max_size = parse_size::parse_size(parts[3])?;
31 if min_size == 0 || avg_size == 0 || max_size == 0 {
32 return Err(anyhow::anyhow!(
33 "Invalid FastCDC segmenter config. Sizes must be greater than 0."
34 ));
35 }
36 if !(min_size <= avg_size && avg_size <= max_size) {
37 return Err(anyhow::anyhow!(
38 "Invalid FastCDC segmenter config. Expected min_size <= avg_size <= max_size."
39 ));
40 }
41 Ok(SegmenterConfig::FastCdc {
42 min_size: min_size as u32,
43 avg_size: avg_size as u32,
44 max_size: max_size as u32,
45 })
46 }
47 "fixed" => {
48 if parts.len() != 2 {
49 return Err(anyhow::anyhow!(
50 "Invalid Fixed segmenter config. Expected format: fixed,size"
51 ));
52 }
53 let size = parse_size::parse_size(parts[1])?;
54 if size == 0 {
55 return Err(anyhow::anyhow!(
56 "Invalid Fixed segmenter config. Size must be greater than 0."
57 ));
58 }
59 Ok(SegmenterConfig::Fixed(size as usize))
60 }
61 _ => Err(anyhow::anyhow!("Unknown segmenter type: {}", parts[0])),
62 }
63}
64
65#[derive(Debug)]
66pub struct Xp3ArchiveBuilder {}
68
69impl Xp3ArchiveBuilder {
70 pub fn new() -> Self {
72 Self {}
73 }
74}
75
76impl ScriptBuilder for Xp3ArchiveBuilder {
77 fn default_encoding(&self) -> Encoding {
78 Encoding::Utf8
79 }
80
81 fn default_archive_encoding(&self) -> Option<Encoding> {
82 Some(Encoding::Utf8)
83 }
84
85 fn build_script(
86 &self,
87 buf: Vec<u8>,
88 _filename: &str,
89 _encoding: Encoding,
90 _archive_encoding: Encoding,
91 config: &ExtraConfig,
92 _archive: Option<&Box<dyn Script>>,
93 ) -> Result<Box<dyn Script>> {
94 Ok(Box::new(Xp3Archive::new(MemReader::new(buf), config)?))
95 }
96
97 fn build_script_from_file(
98 &self,
99 filename: &str,
100 _encoding: Encoding,
101 _archive_encoding: Encoding,
102 config: &ExtraConfig,
103 _archive: Option<&Box<dyn Script>>,
104 ) -> Result<Box<dyn Script>> {
105 let file = std::fs::File::open(filename)?;
106 Ok(Box::new(Xp3Archive::new(file, config)?))
107 }
108
109 fn build_script_from_reader(
110 &self,
111 reader: Box<dyn ReadSeek>,
112 _filename: &str,
113 _encoding: Encoding,
114 _archive_encoding: Encoding,
115 config: &ExtraConfig,
116 _archive: Option<&Box<dyn Script>>,
117 ) -> Result<Box<dyn Script>> {
118 Ok(Box::new(Xp3Archive::new(reader, config)?))
119 }
120
121 fn extensions(&self) -> &'static [&'static str] {
122 &["xp3"]
123 }
124
125 fn script_type(&self) -> &'static ScriptType {
126 &ScriptType::KirikiriXp3
127 }
128
129 fn is_archive(&self) -> bool {
130 true
131 }
132
133 fn create_archive(
134 &self,
135 filename: &str,
136 files: &[&str],
137 _encoding: Encoding,
138 config: &ExtraConfig,
139 ) -> Result<Box<dyn Archive>> {
140 Ok(Box::new(Xp3ArchiveWriter::new(filename, files, config)?))
141 }
142}
143
144#[derive(Debug)]
145pub struct Xp3Archive<T: Read + Seek + std::fmt::Debug> {
147 reader: Arc<Mutex<T>>,
148 entries: Vec<(String, XP3FileIndex)>,
149 decrypt_simple_crypt: bool,
150 decompress_mdf: bool,
151}
152
153impl<T: Read + Seek + std::fmt::Debug> Xp3Archive<T> {
154 pub fn new(reader: T, config: &ExtraConfig) -> Result<Self> {
156 let xp3_reader = XP3Reader::open_archive(reader)
157 .map_err(|e| anyhow::anyhow!("Failed to open XP3 archive: {:?}", e))?;
158 let entries = xp3_reader
159 .entries()
160 .filter_map(|(i, d)| {
161 if i.find("$$$ This is a protected archive. $$$").is_some()
163 || (i.to_lowercase().ends_with(".nene") && d.info().file_size() == 0)
164 {
165 None
166 } else {
167 Some((i.clone(), d.clone()))
168 }
169 })
170 .collect();
171 Ok(Self {
172 reader: Arc::new(Mutex::new(xp3_reader.close().1)),
173 entries,
174 decrypt_simple_crypt: config.xp3_simple_crypt,
175 decompress_mdf: config.xp3_mdf_decompress,
176 })
177 }
178}
179
180impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
181 fn default_output_script_type(&self) -> OutputScriptType {
182 OutputScriptType::Json
183 }
184
185 fn default_format_type(&self) -> FormatOptions {
186 FormatOptions::None
187 }
188
189 fn is_archive(&self) -> bool {
190 true
191 }
192
193 fn iter_archive_filename<'a>(
194 &'a self,
195 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
196 Ok(Box::new(
197 self.entries.iter().map(|entry| Ok(entry.0.clone())),
198 ))
199 }
200
201 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
202 let index = self
203 .entries
204 .iter()
205 .nth(index)
206 .ok_or(anyhow::anyhow!("Index out of bounds: {}", index))?
207 .1
208 .clone();
209 let mut entry = Entry::new(self.reader.clone(), index);
210 let mut header = [0u8; 16];
211 let header_len = entry.read(&mut header)?;
212 entry.rewind()?;
213 entry.script_type = detect_script_type(entry.index.info().name(), &header, header_len);
214 if self.decrypt_simple_crypt
215 && header_len >= 5
216 && header[0] == 0xFE
217 && header[1] == 0xFE
218 && header[3] == 0xFF
219 && header[4] == 0xFE
220 {
221 let crypt = header[2];
222 if crypt == 2 {
223 let index = entry.index.clone();
224 return Ok(Box::new(SimpleCryptZlib::new(entry, index)?));
225 }
226 if matches!(crypt, 0 | 1) {
227 let index = entry.index.clone();
228 return Ok(Box::new(SimpleCrypt::new(entry, index, crypt)?));
229 }
230 }
231 if self.decompress_mdf
232 && header_len >= 4
233 && &header[0..4] == b"mdf\0"
234 && entry.index.info().file_size() > 8
235 {
236 let index = entry.index.clone();
237 return Ok(Box::new(MdfEntry::new(entry, index)?));
238 }
239 Ok(Box::new(entry))
240 }
241}
242
243fn detect_script_type(filename: &str, buf: &[u8], buf_len: usize) -> Option<ScriptType> {
244 #[cfg(feature = "kirikiri-img")]
245 if buf_len >= 11 && libtlg_rs::is_valid_tlg(buf) {
246 return Some(ScriptType::KirikiriTlg);
247 }
248 if buf_len >= 8 && (buf.starts_with(b"TJS/ns0\0") || buf.starts_with(b"TJS/4s0\0")) {
249 return Some(ScriptType::KirikiriTjsNs0);
250 }
251 if buf_len >= 8 && buf.starts_with(b"TJS2100\0") {
252 return Some(ScriptType::KirikiriTjs2);
253 }
254 let extension = std::path::Path::new(filename)
255 .extension()
256 .and_then(|s| s.to_str())
257 .unwrap_or("")
258 .to_lowercase();
259 match extension.as_str() {
260 "ks" => Some(ScriptType::Kirikiri),
261 "scn" => Some(ScriptType::KirikiriScn),
262 #[cfg(feature = "emote-img")]
263 "dref" => Some(ScriptType::EmoteDref),
264 #[cfg(feature = "emote-img")]
265 "pimg" => Some(ScriptType::EmotePimg),
266 _ => None,
267 }
268}
269
270#[derive(Debug)]
271struct Entry<T: Read + Seek + std::fmt::Debug> {
272 reader: Arc<Mutex<T>>,
273 index: XP3FileIndex,
274 cache: Option<ZlibDecoder<Take<MutexWrapper<T>>>>,
275 pos: u64,
276 entries_pos: Vec<u64>,
277 script_type: Option<ScriptType>,
278}
279
280impl<T: Read + Seek + std::fmt::Debug> Entry<T> {
281 fn new(reader: Arc<Mutex<T>>, index: XP3FileIndex) -> Self {
282 let mut pos = 0;
283 let entries_pos = index
284 .segments()
285 .iter()
286 .map(|seg| {
287 let p = pos;
288 pos += seg.original_size();
289 p
290 })
291 .collect();
292 Self {
293 reader,
294 index,
295 cache: None,
296 pos: 0,
297 entries_pos,
298 script_type: None,
299 }
300 }
301}
302
303impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for Entry<T> {
304 fn name(&self) -> &str {
305 &self.index.info().name()
306 }
307
308 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
309 Ok(Box::new(self))
310 }
311
312 fn script_type(&self) -> Option<&ScriptType> {
313 self.script_type.as_ref()
314 }
315}
316
317impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
318 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
319 if self.pos >= self.index.info().file_size() {
320 self.cache.take();
321 return Ok(0);
322 }
323 if let Some(cache) = self.cache.as_mut() {
324 let readed = cache.read(buf)?;
325 if readed > 0 {
326 self.pos += readed as u64;
327 return Ok(readed);
328 }
329 self.cache.take();
330 }
331 let seg_index = match self.entries_pos.binary_search(&self.pos) {
332 Ok(i) => i,
333 Err(i) => {
334 if i == 0 {
335 0
336 } else {
337 i - 1
338 }
339 }
340 };
341 let seg = &self.index.segments()[seg_index];
342 let start_pos = seg.data_offset();
343 let seg_pos = self.entries_pos[seg_index];
344 let skip_pos = self.pos - seg_pos;
345 let read_size = seg.saved_size();
346 match seg.flag() {
347 IndexSegmentFlag::UnCompressed => {
348 let mut lock = MutexWrapper::new(self.reader.clone(), start_pos + skip_pos);
349 let readed = (&mut lock).take(read_size - skip_pos).read(buf)?;
350 self.pos += readed as u64;
351 Ok(readed)
352 }
353 IndexSegmentFlag::Compressed => {
354 let mut cache = ZlibDecoder::new(
355 MutexWrapper::new(self.reader.clone(), start_pos).take(read_size),
356 );
357 if skip_pos != 0 {
358 let mut e = EmptyWriter::new();
359 std::io::copy(&mut (&mut cache).take(skip_pos), &mut e)?; }
361 let readed = cache.read(buf)?;
362 self.pos += readed as u64;
363 self.cache = Some(cache);
364 Ok(readed)
365 }
366 }
367 }
368}
369
370impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
371 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
372 let new_pos = match pos {
373 SeekFrom::Start(p) => p,
374 SeekFrom::End(offset) => {
375 if offset < 0 {
376 if (-offset) as u64 > self.index.info().file_size() {
377 return Err(std::io::Error::new(
378 std::io::ErrorKind::InvalidInput,
379 "Seek from end exceeds file length",
380 ));
381 }
382 self.index.info().file_size() - (-offset) as u64
383 } else {
384 self.index.info().file_size() + offset as u64
385 }
386 }
387 SeekFrom::Current(offset) => {
388 if offset < 0 {
389 if (-offset) as u64 > self.pos {
390 return Err(std::io::Error::new(
391 std::io::ErrorKind::InvalidInput,
392 "Seek from current exceeds file start",
393 ));
394 }
395 self.pos - (-offset) as u64
396 } else {
397 self.pos + offset as u64
398 }
399 }
400 };
401 if let Some(cache) = self.cache.as_mut() {
402 let old_seg_index = match self.entries_pos.binary_search(&self.pos) {
403 Ok(i) => i,
404 Err(i) => {
405 if i == 0 {
406 0
407 } else {
408 i - 1
409 }
410 }
411 };
412 let new_seg_index = match self.entries_pos.binary_search(&new_pos) {
413 Ok(i) => i,
414 Err(i) => {
415 if i == 0 {
416 0
417 } else {
418 i - 1
419 }
420 }
421 };
422 if old_seg_index != new_seg_index {
423 self.cache.take();
424 } else {
425 if new_pos >= self.pos {
426 let skip_pos = new_pos - self.pos;
427 let mut e = EmptyWriter::new();
428 std::io::copy(&mut cache.take(skip_pos), &mut e)?; } else {
430 self.cache.take();
431 }
432 }
433 }
434 self.pos = new_pos;
435 Ok(self.pos)
436 }
437
438 fn rewind(&mut self) -> std::io::Result<()> {
439 self.pos = 0;
440 self.cache.take();
441 Ok(())
442 }
443
444 fn stream_position(&mut self) -> std::io::Result<u64> {
445 Ok(self.pos)
446 }
447}
448
449struct SimpleCryptZlib<T: Read + Seek + std::fmt::Debug> {
450 inner: PrefixStream<ZlibDecoder<StreamRegion<Entry<T>>>>,
451 index: XP3FileIndex,
452}
453
454impl<T: Read + Seek + std::fmt::Debug> SimpleCryptZlib<T> {
455 fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
456 entry.seek(SeekFrom::Start(0x15))?;
457 let entry = StreamRegion::new(entry, 0x15, index.info().file_size())?;
458 let inner = PrefixStream::new(vec![0xFF, 0xFE], ZlibDecoder::new(entry));
459 Ok(Self { inner, index })
460 }
461}
462
463impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for SimpleCryptZlib<T> {
464 fn name(&self) -> &str {
465 &self.index.info().name()
466 }
467}
468
469impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptZlib<T> {
470 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
471 self.inner.read(buf)
472 }
473}
474
475#[derive(Debug)]
476struct SimpleCryptInner<T: Read + Seek + std::fmt::Debug> {
477 inner: StreamRegion<Entry<T>>,
478 crypt: u8,
479}
480
481impl<T: Read + Seek + std::fmt::Debug> SimpleCryptInner<T> {
482 fn new(mut entry: Entry<T>, crypt: u8) -> Result<Self> {
483 entry.seek(SeekFrom::Start(5))?;
484 let size = entry.index.info().file_size();
485 let entry = StreamRegion::new(entry, 5, size)?;
486 Ok(Self {
487 inner: entry,
488 crypt,
489 })
490 }
491}
492
493impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptInner<T> {
494 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
495 let readed = self.inner.read(buf)?;
496 match self.crypt {
497 0 => {
498 for b in &mut buf[..readed] {
499 let ch = *b as u16;
500 if ch >= 20 {
501 *b = wrapping! {ch ^ (((ch & 0xfe) << 8) ^ 1)} as u8;
502 }
503 }
504 }
505 1 => {
506 for b in &mut buf[..readed] {
507 let mut ch = *b as u32;
508 ch = wrapping! {((ch & 0xaaaaaaaa) >> 1) | ((ch & 0x55555555) << 1)};
509 *b = ch as u8;
510 }
511 }
512 _ => {}
513 }
514 Ok(readed)
515 }
516}
517
518impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCryptInner<T> {
519 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
520 self.inner.seek(pos)
521 }
522
523 fn rewind(&mut self) -> std::io::Result<()> {
524 self.inner.rewind()
525 }
526
527 fn stream_position(&mut self) -> std::io::Result<u64> {
528 self.inner.stream_position()
529 }
530}
531
532#[derive(Debug)]
533struct SimpleCrypt<T: Read + Seek + std::fmt::Debug> {
534 inner: PrefixStream<SimpleCryptInner<T>>,
535 index: XP3FileIndex,
536}
537
538impl<T: Read + Seek + std::fmt::Debug> SimpleCrypt<T> {
539 fn new(entry: Entry<T>, index: XP3FileIndex, crypt: u8) -> Result<Self> {
540 let inner = PrefixStream::new(vec![0xFF, 0xFE], SimpleCryptInner::new(entry, crypt)?);
541 Ok(Self { inner, index })
542 }
543}
544
545impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for SimpleCrypt<T> {
546 fn name(&self) -> &str {
547 &self.index.info().name()
548 }
549
550 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
551 Ok(Box::new(self))
552 }
553}
554
555impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCrypt<T> {
556 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
557 self.inner.read(buf)
558 }
559}
560
561impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCrypt<T> {
562 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
563 self.inner.seek(pos)
564 }
565
566 fn rewind(&mut self) -> std::io::Result<()> {
567 self.inner.rewind()
568 }
569
570 fn stream_position(&mut self) -> std::io::Result<u64> {
571 self.inner.stream_position()
572 }
573}
574
575#[derive(Debug)]
576struct MdfEntry<T: Read + Seek + std::fmt::Debug> {
577 inner: ZlibDecoder<StreamRegion<Entry<T>>>,
578 index: XP3FileIndex,
579}
580
581impl<T: Read + Seek + std::fmt::Debug> MdfEntry<T> {
582 fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
583 entry.seek(SeekFrom::Start(8))?;
584 let entry = StreamRegion::new(entry, 8, index.info().file_size())?;
585 let inner = ZlibDecoder::new(entry);
586 Ok(Self { inner, index })
587 }
588}
589
590impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for MdfEntry<T> {
591 fn name(&self) -> &str {
592 &self.index.info().name()
593 }
594}
595
596impl<T: Read + Seek + std::fmt::Debug> Read for MdfEntry<T> {
597 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
598 self.inner.read(buf)
599 }
600}